package com.mobilezu.mobile.pushserver.xmpp.net;

import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import java.util.Random;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Element;
import org.dom4j.io.XMPPPacketReader;
import org.jivesoftware.openfire.net.MXParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.IQ;
import org.xmpp.packet.Message;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence;
import org.xmpp.packet.Roster;
import org.xmpp.packet.StreamError;
import com.mobilezu.mobile.pushserver.model.PushData;
import com.mobilezu.mobile.pushserver.service.PushDataService;
import com.mobilezu.mobile.pushserver.service.ServiceManager;
import com.mobilezu.mobile.pushserver.util.Config;
import com.mobilezu.mobile.pushserver.util.Constants;
import com.mobilezu.mobile.pushserver.xmpp.push.NotificationManager;
import com.mobilezu.mobile.pushserver.xmpp.router.PacketRouter;
import com.mobilezu.mobile.pushserver.xmpp.session.ClientSession;
import com.mobilezu.mobile.pushserver.xmpp.session.Session;
import com.mobilezu.mobile.pushserver.xmpp.session.SessionManager;


public class StanzaHandler {

	private static final Log log = LogFactory.getLog(StanzaHandler.class);

	protected Connection connection;
	protected Session session;
	protected String serverName;
	private boolean sessionCreated = false;
	private boolean startedTLS = false;
	private PacketRouter router;
	private NotificationManager notificationManager;
	private PushDataService pushDataService;
	private SessionManager sessionManager;

	/**
	 * ������.
	 * 
	 * @param serverName
	 *            the server name
	 * @param connection
	 *            the connection
	 */
	public StanzaHandler(String serverName, Connection connection) {
		this.serverName = serverName;
		this.connection = connection;
		this.router = new PacketRouter();
		notificationManager = new NotificationManager();
		pushDataService = ServiceManager.getPushDataService();
		sessionManager = SessionManager.getInstance();
	}

	/**
	 * Process the received stanza using the given XMPP packet reader.
	 * 
	 * @param stanza
	 *            the received statza
	 * @param reader
	 *            the XMPP packet reader
	 * @throws Exception
	 *             if the XML stream is not valid.
	 */
	public void process(String stanza, XMPPPacketReader reader)
			throws Exception {
		boolean initialStream = stanza.startsWith("<stream:stream");
		if (!sessionCreated || initialStream) {
			if (!initialStream) {
				return; // Ignore <?xml version="1.0"?>
			}
			if (!sessionCreated) {
				sessionCreated = true;
				MXParser parser = reader.getXPPParser();
				parser.setInput(new StringReader(stanza));
				createSession(parser);
			} else if (startedTLS) {
				startedTLS = false;
				tlsNegotiated();
			}
			return;
		}

		// If end of stream was requested
		if (stanza.equals("</stream:stream>")) {
			session.close();
			return;
		}
		// Ignore <?xml version="1.0"?>
		if (stanza.startsWith("<?xml")) {
			return;
		}
		// Create DOM object
		Element doc = reader.read(new StringReader(stanza)).getRootElement();
		if (doc == null) {
			return;
		}

		String tag = doc.getName();
		if ("starttls".equals(tag)) {
			if (negotiateTLS()) { // Negotiate TLS
				startedTLS = true;
			} else {
				connection.close();
				session = null;
			}
		} else if ("message".equals(tag)) {
			processMessage(doc);
		} else if ("presence".equals(tag)) {
			log.debug("presence...");
			processPresence(doc);
		} else if ("iq".equals(tag)) {
			log.debug("iq...");
			processIQ(doc);
		} else {
			log.warn("Unexpected packet tag (not message, iq, presence)"
					+ doc.asXML());
			session.close();
		}

	}

	private void processMessage(Element doc) {
		log.debug("processMessage()...");
		Message packet;
		try {
			packet = new Message(doc, false);
		} catch (IllegalArgumentException e) {
			log.debug("Rejecting packet. JID malformed", e);
			Message reply = new Message();
			reply.setID(doc.attributeValue("id"));
			reply.setTo(session.getAddress());
			reply.getElement().addAttribute("from", doc.attributeValue("to"));
			reply.setError(PacketError.Condition.jid_malformed);
			session.process(reply);
			return;
		}

		packet.setFrom(session.getAddress());
		router.route(packet);
		session.incrementClientPacketCount();
	}

	/**
	 * �û���¼����ˣ����Կ�ʼ������Ϣ�ˣ����Դ���������Ϣ
	 * 
	 * @param doc
	 * @throws Exception
	 */
	private void processPresence(Element doc) {
		log.debug("processPresence()...");
		Presence packet;
		try {
			packet = new Presence(doc, false);
		} catch (IllegalArgumentException e) {
			log.debug("Rejecting packet. JID malformed", e);
			Presence reply = new Presence();
			reply.setID(doc.attributeValue("id"));
			reply.setTo(session.getAddress());
			reply.getElement().addAttribute("from", doc.attributeValue("to"));
			reply.setError(PacketError.Condition.jid_malformed);
			session.process(reply);
			return;
		}
		if (session.getStatus() == Session.STATUS_CLOSED
				&& packet.isAvailable()) {
			log.warn("Ignoring available presence packet of closed session: "
					+ packet);
			return;
		}

		packet.setFrom(session.getAddress());
		router.route(packet);
		session.incrementClientPacketCount();

		// �û����ߣ���ѯ���û���δ���ͻ�û���յ���ִ����Ϣ�����·��͡�
		if (session.getStatus() == Session.STATUS_AUTHENTICATED
				&& packet.isAvailable()) {
			String recvUsername = session.getAddress().getNode();
			if (null != recvUsername && !"".equals(recvUsername)) {
				PushData pushData = new PushData();
				pushData.setRecvUsername(recvUsername);
				List<PushData> pushDataList = pushDataService
						.queryPushData(pushData);
				if (pushDataList != null && !pushDataList.isEmpty()) {
					for (PushData pushData2 : pushDataList) {
						if (pushData2.getStatus().equals(
								Constants.STATUS_NOT_YET_SEND)
								|| pushData2.getStatus().equals(
										Constants.STATUS_ALREADY_SEND)) {
							notificationManager
									.sendOfflineNotification(pushData2);
						}
					}
				} else {
					log.info(" no offline notification, recvUsername = "
							+ recvUsername);
				}
			} else {
				log.warn("recvUsername is null ! ");
			}
		}

	}

	private void processIQ(Element doc) {
		log.debug("processIQ()...");
		IQ packet;
		try {
			packet = getIQ(doc);
		} catch (IllegalArgumentException e) {
			log.debug("Rejecting packet. JID malformed", e);
			IQ reply = new IQ();
			if (!doc.elements().isEmpty()) {
				reply.setChildElement(((Element) doc.elements().get(0))
						.createCopy());
			}
			reply.setID(doc.attributeValue("id"));
			reply.setTo(session.getAddress());
			String to = doc.attributeValue("to");
			if (to != null) {
				reply.getElement().addAttribute("from", to);
			}
			reply.setError(PacketError.Condition.jid_malformed);
			session.process(reply);
			return;
		}
		packet.setFrom(session.getAddress());
		router.route(packet);
		session.incrementClientPacketCount();
	}

	private IQ getIQ(Element doc) {
		Element query = doc.element("query");
		if (query != null && "jabber:iq:roster".equals(query.getNamespaceURI())) {
			return new Roster(doc);
		} else {
			// �����û��Ѿ���¼�ˣ��Ѿ���¼���û��ͱ�������
			if (query != null
					&& "jabber:iq:auth".equals(query.getNamespaceURI())
					&& query.element("username") != null
					&& query.element("digest") == null) {
				String closeClientAppUsername = query.element("username")
						.getText();
				ClientSession closeClientAppSession = sessionManager
						.getSession(closeClientAppUsername);
				if (closeClientAppSession != null
						&& closeClientAppSession.getPresence().isAvailable()) {
					notificationManager
							.sendCloseSessionNotification(closeClientAppSession);
					closeClientAppSession.close();
					log.debug("Be forced logout: " + closeClientAppUsername);
				}
			}
			return new IQ(doc, false);
		}
	}

	private void createSession(XmlPullParser xpp)
			throws XmlPullParserException, IOException {
		for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
			eventType = xpp.next();
		}
		// Create the correct session based on the sent namespace
		String namespace = xpp.getNamespace(null);
		if ("jabber:client".equals(namespace)) {
			session = ClientSession.createSession(serverName, connection, xpp);
			if (session == null) {
				StringBuilder sb = new StringBuilder(250);
				sb.append("<?xml version='1.0' encoding='UTF-8'?>");
				sb.append("<stream:stream from=\"").append(serverName);
				sb.append("\" id=\"").append(randomString(5));
				sb.append("\" xmlns=\"").append(xpp.getNamespace(null));
				sb.append("\" xmlns:stream=\"").append(
						xpp.getNamespace("stream"));
				sb.append("\" version=\"1.0\">");

				// bad-namespace-prefix in the response
				StreamError error = new StreamError(
						StreamError.Condition.bad_namespace_prefix);
				sb.append(error.toXML());
				connection.deliverRawText(sb.toString());
				connection.close();
				log.warn("Closing session due to bad_namespace_prefix in stream header: "
						+ namespace);
			}
		}
	}

	private boolean negotiateTLS() {
		if (connection.getTlsPolicy() == Connection.TLSPolicy.disabled) {
			// Set the not_authorized error
			StreamError error = new StreamError(
					StreamError.Condition.not_authorized);
			connection.deliverRawText(error.toXML());
			connection.close();
			log.warn("TLS requested by initiator when TLS was never offered"
					+ " by server. Closing connection : " + connection);
			return false;
		}
		// Client requested to secure the connection using TLS.
		try {
			startTLS();
		} catch (Exception e) {
			log.error("Error while negotiating TLS", e);
			connection
					.deliverRawText("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
			connection.close();
			return false;
		}
		return true;
	}

	private void startTLS() throws Exception {
		Connection.ClientAuth policy;
		try {
			policy = Connection.ClientAuth.valueOf(Config.getString(
					"xmpp.client.cert.policy", "disabled"));
		} catch (IllegalArgumentException e) {
			policy = Connection.ClientAuth.disabled;
		}
		connection.startTLS(policy);
	}

	private void tlsNegotiated() {
		// Offer stream features including SASL Mechanisms
		StringBuilder sb = new StringBuilder(620);
		sb.append("<?xml version='1.0' encoding='UTF-8'?>");
		sb.append("<stream:stream ");
		sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" ");
		sb.append("xmlns=\"jabber:client\" from=\"");
		sb.append(serverName);
		sb.append("\" id=\"");
		sb.append(session.getStreamID());
		sb.append("\" xml:lang=\"");
		sb.append(connection.getLanguage());
		sb.append("\" version=\"");
		sb.append(Session.MAJOR_VERSION).append(".")
				.append(Session.MINOR_VERSION);
		sb.append("\">");
		sb.append("<stream:features>");
		// Include specific features such as auth and register for client
		// sessions
		String specificFeatures = session.getAvailableStreamFeatures();
		if (specificFeatures != null) {
			sb.append(specificFeatures);
		}
		sb.append("</stream:features>");
		connection.deliverRawText(sb.toString());
	}

	private String randomString(int length) {
		if (length < 1) {
			return null;
		}
		char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz"
				+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
		char[] randBuffer = new char[length];
		for (int i = 0; i < randBuffer.length; i++) {
			randBuffer[i] = numbersAndLetters[new Random().nextInt(71)];
		}
		return new String(randBuffer);
	}

}
